package com.ruoyi.common.gscUtil.StudyUtil.redisUtil;

//import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.ruoyi.common.core.redis.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * redis的《***缓存击穿***》解决方案
 * 缓存击穿是指某个热点缓存（高并发访问的缓存）在失效的瞬间，大量请求直接打到数据库，无法从redis缓存中读取，导致数据库压力骤增。
 *  在缓存失效的瞬间，有大量线程来重建缓存，造成后端负载加大，甚至可能会让应用崩溃。
 *
 *  与缓存雪崩不同，缓存击穿通常针对单个热点数据
 *  缓存击穿的解决方案核心思想是：
 * 1.避免缓存失效：通过设置永不过期或提前刷新缓存。
 * 2.控制并发访问：通过互斥锁或缓存标记，确保只有一个线程加载数据。
 * 3.减少数据库压力：通过二级缓存、限流和降级策略，保护数据库。
 *  *互斥锁适用于高并发场景，防止多个线程同时查询数据库。
 *  *限流与降级适用于极端情况，保护数据库不被压垮。
 * @author gsc
 * @date 2025/2/18
 */
public class redisBreakUtil {
    @Autowired
    private RedisTemplate redisTemplate;
    @Resource
    private RedisCache redisCache;

    //1.热点数据永不过期  这种方案由于没有设置真正的过期时间，实际上已经不存在热点key产生的一系列危害，但是会存在《数据不一致》的情况，同时代码复杂度会增大。
    //对于热点数据，可以设置缓存永不过期，通过后台任务定期更新缓存，避免缓存失效。
    //后台任务定期更新缓存
    public void refreshCache() {
        redisCache.setCacheObject("hot:key", "value");
        String newValue = fetchDataFromDatabase("hot:key");
        redisCache.setCacheObject("hot:key", newValue);
    }
    private String fetchDataFromDatabase(String s) {
        return "data object";//拿到数据库中的数据
    }


    //2.缓存标记（Cache Mark）
    //为缓存设置一个标记，标记缓存是否正在更新。当缓存失效时，先检查标记，如果标记存在，则等待缓存更新完成后再读取缓存
    public String cacheMark(String key) {
        String value = redisCache.getCacheObject(key);
        if (value == null) { // 缓存失效
            String markKey = "mark:" + key; // 缓存更新标记
            redisTemplate.opsForValue().set(markKey, "1");
            if (redisTemplate.opsForValue().get(markKey) == "1") { // 设置缓存更新标记
                redisCache.expire(markKey, 10);//// 设置标记的过期时间
                value = fetchDataFromDatabase(key); // 从数据库加载数据
                redisCache.setCacheObject(key, value,60 * 60, TimeUnit.SECONDS);//// 设置缓存
                redisCache.deleteObject(markKey); // 删除缓存更新标记
            } else {
                // 等待缓存更新完成
                while (redisCache.hasKey(markKey)) {
                    try {
                        Thread.sleep(100); // 等待 100ms
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                }
                value = redisCache.getCacheObject(key); // 重新尝试获取缓存
            }
        }
        return value;
    }


    //3.提前刷新缓存
    //在缓存即将过期时，提前异步刷新缓存，避免缓存失效后大量请求直接打到数据库。
    public String advanceRefresh(String key) {
        String value = redisCache.getCacheObject(key);
        if (value == null) { // 缓存失效
            value = fetchDataFromDatabase(key); // 从数据库加载数据
            redisCache.setCacheObject(key, value,60 * 60, TimeUnit.SECONDS);// 设置缓存
        } else {
            long ttl = redisCache.getExpire(key);
            if (ttl < 60) { // 缓存即将过期
                // 异步刷新缓存
                CompletableFuture.runAsync(() -> {
                    String newValue = fetchDataFromDatabase(key);
                    redisCache.setCacheObject(key, newValue,60 * 60, TimeUnit.SECONDS);// 设置缓存
                });
            }
        }
        return value;
    }


    //4.使用互斥锁（Mutex Lock）
    //当缓存失效时，使用互斥锁来确保只有一个线程去加载数据，其他线程等待。
    //这种方案思路比较简单，但是存在一定的隐患，如果在查询数据库 + 和 重建缓存（key失效后进行了大量的计算）时间过长，
    // 也可能会存在死锁和线程池阻塞的风险，高并发情景下吞吐量会大大降低！但是这种方法能够较好地降低后端存储负载，并在一致性上做得比较好
    public Object getMutexLock(String key) {
        Object value = redisTemplate.opsForValue().get(key);
        if (value == null) {
            // 缓存失效，尝试获取锁
            String lockKey = key + ":lock";
            boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS); //Absent:不在
            if (locked) {
                try {
                    // 从数据库加载数据
                    value = loadDataFromDB(key);
                    // 重新设置缓存
                    redisTemplate.opsForValue().set(key, value, 60, TimeUnit.MINUTES);
                } finally {
                    // 释放锁
                    redisTemplate.delete(lockKey);
                }
            } else {
                // 未获取到锁，等待一段时间后重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return redisCache.getCacheObject(key); // 重试
            }
        }
        return value;
    }
    private Object loadDataFromDB(String key) {
        // 模拟从数据库加载数据
        return "data_from_db";
    }


    //5.使用限流和降级
    //在缓存失效时，使用限流和降级策略，避免大量请求直接打到数据库。可以使用Hystrix等工具实现限流和降级。
    // 使用Hystrix实现限流和降级
//    @HystrixCommand(fallbackMethod = "getDataFallback") //转向降级方法 ⬇ ⬇
    public String hystrixData(String key) {
        String value = redisCache.getCacheObject(key);
        if (value == null) { // 缓存失效
            value = loadDataFromDB(key).toString(); // 从数据库加载数据
            redisCache.setCacheObject(key,value,60 * 60,TimeUnit.SECONDS);// 设置缓存
        }
        return value;
    }
    public String getDataFallback(String key) {//降级处理
        return "fallback value"; //  降级逻辑，例如返回默认值、缓存数据或提示信息等
    }


}
